package com.jms.tomcat.core;

import com.jms.tomcat.annotation.JmsFilterScan;
import com.jms.tomcat.annotation.JmsServletScan;
import com.jms.tomcat.config.JmsTomcatConfigItem;
import com.jms.tomcat.config.TomcatConfig;
import com.jms.tomcat.http.JmsServletError;
import com.jms.tomcat.initializer.JmsAbsServletInitializer;
import com.jms.tomcat.mapping.FilterMapping;
import com.jms.tomcat.mapping.ServletMapping;
import com.jms.tomcat.servlet.DefaultServlet;
import com.jms.tomcat.filter.JmsFilterChain;
import com.jms.tomcat.registration.JmsFilterRegistration;
import com.jms.tomcat.registration.JmsServletRegistration;
import com.jms.tomcat.util.PackageUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.descriptor.JspConfigDescriptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;

/**
 * servlet环境上下文
 */
public class JmsServletContext extends JmsAbsServletInitializer implements ServletContext {

    private static final Logger logger = LogManager.getLogger(JmsServletContext.class);
    /**
     * 静态资源路径
     */
    private String[] staticResourcePath;
    /**
     * servlet名称 与 servlet注册器的映射
     */
    private final Map<String, JmsServletRegistration> servletRegistrationMap = new HashMap<>();
    /**
     * Filter名称 与 Filter注册器的映射
     */
    private final Map<String, JmsFilterRegistration> filterRegistrationMap = new HashMap<>();
    /**
     * servlet映射列表
     */
    private final List<ServletMapping> servletMappingList = new ArrayList<>();
    /**
     * Filter映射列表
     */
    private final List<FilterMapping> filterMappingList = new ArrayList<>();

    public JmsServletContext(Class<?> configClass, TomcatConfig tomcatConfig) {
        super(configClass, tomcatConfig);
    }

    public List<ServletMapping> getServletMappingList() {
        return servletMappingList;
    }

    public List<FilterMapping> getFilterMappingList() {
        return filterMappingList;
    }

    public void init() {
        //处理静态资源映射
        initStaticResourcePath();

        //注册servlet
        registerServlet();

        //注册Filter
        registerFilter();

        //注册Listener
        registerListener();

        //加载servlet容器初始化器
        loadServletContainerInitializer();

        //初始化servlet
        initServlets();

        //初始化Filter
        initFilters();

        //初始化Listener
        initListeners();
    }

    @Override
    public void initStaticResourcePath() {
        logger.info("Start working on static resources.");
        staticResourcePath = tomcatConfig.getString(JmsTomcatConfigItem.JMS_TOMCAT_RESOURCE_PATH.getValue()).split(",");
        for (String resourcePathItem: staticResourcePath) {
            String uri = resourcePathItem.endsWith("/") ? resourcePathItem + "*" : resourcePathItem + "/*";
            ServletRegistration.Dynamic registration = addServlet(DefaultServlet.class.getSimpleName(), new DefaultServlet());
            registration.addMapping(uri);
        }
        logger.info("Static resources are processed.");
    }

    @Override
    @SuppressWarnings("unchecked")
    public void registerServlet() {
        logger.info("Start registering servlet.");
        //从配置里获取扫描信息
        String scanPathStr = tomcatConfig.getString(JmsTomcatConfigItem.JMS_TOMCAT_RESOURCE_PATH.getValue());
        String[] scanPath = null;
        if (scanPathStr != null) {
            scanPath = scanPathStr.split(",");
        } else {
            //获取配置类的扫描注解
            if (configClass.isAnnotationPresent(JmsServletScan.class)) {
                scanPath = configClass.getAnnotation(JmsServletScan.class).value();
            }
        }
        if (scanPath == null) {
            logger.debug("The servlet scan path is null.");
            return;
        }
        //获取扫描目录下所有类的全限定类名
        ArrayList<String> fullyLimitedClassNames = new ArrayList<>();
        try {
            fullyLimitedClassNames = (ArrayList<String>) PackageUtil.getClassNameByPackageName(true, Objects.requireNonNull(scanPath));
        } catch (IOException e) {
            logger.error("Failed to get the class name.", e);
        }
        for (String s: fullyLimitedClassNames) {
            try {
                Class<? extends Servlet> cl = (Class<? extends Servlet>) Class.forName(s);
                if (cl.isAnnotationPresent(WebServlet.class)) {
                    WebServlet webServlet = cl.getAnnotation(WebServlet.class);
                    String[] uris = webServlet.value();
                    String name = webServlet.name() != null && !"".equals(webServlet.name()) ? webServlet.name() : cl.getSimpleName();
                    WebInitParam[] initParams = webServlet.initParams();
                    ServletRegistration.Dynamic registration = addServlet(name, cl);
                    registration.addMapping(uris);
                    for (WebInitParam initParam: initParams) {
                        registration.setInitParameter(initParam.name(), initParam.value());
                    }
                }
            } catch (ClassNotFoundException e) {
                logger.error(String.format("Not find the class: %s.", s), e);
            }
        }
        logger.info("Servlet registration is complete.");
    }

    @Override
    @SuppressWarnings("unchecked")
    public void registerFilter() {
        logger.info("Start registering filter.");
        //从配置里获取扫描信息
        String filterPathStr = tomcatConfig.getString(JmsTomcatConfigItem.JMS_TOMCAT_RESOURCE_PATH.getValue());
        String[] filterPath = null;
        if (filterPathStr != null) {
            filterPath = filterPathStr.split(",");
        } else {
            //获取配置类的扫描注解
            if (configClass.isAnnotationPresent(JmsFilterScan.class)) {
                filterPath = configClass.getAnnotation(JmsFilterScan.class).value();
            }
        }
        if (filterPath == null) {
            logger.debug("The filter scan path is null.");
            return;
        }
        //获取扫描目录下所有类的全限定类名
        ArrayList<String> fullyLimitedClassNames = new ArrayList<>();
        try {
            fullyLimitedClassNames = (ArrayList<String>) PackageUtil.getClassNameByPackageName(true, Objects.requireNonNull(filterPath));
        } catch (IOException e) {
            logger.error("Failed to get the class name.", e);
        }
        for (String s: fullyLimitedClassNames) {
            try {
                Class<? extends Filter> cl = (Class<? extends Filter>) Class.forName(s);
                if (cl.isAnnotationPresent(WebFilter.class)) {
                    WebFilter webFilter = cl.getAnnotation(WebFilter.class);
                    String[] uris = webFilter.value();
                    String name = webFilter.filterName() != null && !"".equals(webFilter.filterName()) ? webFilter.filterName() : cl.getSimpleName();
                    WebInitParam[] initParams = webFilter.initParams();
                    FilterRegistration.Dynamic registration = addFilter(name, cl);
                    registration.addMappingForUrlPatterns(null, false, uris);
                    for (WebInitParam initParam: initParams) {
                        registration.setInitParameter(initParam.name(), initParam.value());
                    }
                }
            } catch (ClassNotFoundException e) {
                logger.error(String.format("Not find the class: %s.", s), e);
            }
        }
        logger.info("Filter registration is complete.");
    }

    @Override

    public void loadServletContainerInitializer() {
        try {
            logger.info("Start loading ServletContainerInitializer.");
            ServiceLoader<ServletContainerInitializer> sciLoader = ServiceLoader.load(ServletContainerInitializer.class);
            Set<Class<?>> classes = new HashSet<>();
            for (ServletContainerInitializer sci : sciLoader) {
                try {
                    sci.onStartup(classes, this);
                } catch (Exception e) {
                    logger.error("Failed to load ServletContainerInitializer: " + sci.getClass().getName(), e);
                }
            }
            logger.info("ServletContainerInitializer is loaded.");
        } catch (Exception e) {
            logger.error("Failed to load ServletContainerInitializer.", e);
        }
    }

    @Override
    public void initServlets() {
        logger.info("Start initializing the servlet.");
        for (JmsServletRegistration jmsServletRegistration: servletRegistrationMap.values()) {
            try {
                if (jmsServletRegistration.isInitialized()) {
                    continue;
                }
                //初始化servlet
                jmsServletRegistration.getServlet().init(jmsServletRegistration.getServletConfig());
                //添加servletMapping
                for (String uri: jmsServletRegistration.getMappings()) {
                    servletMappingList.add(new ServletMapping(jmsServletRegistration.getServlet(), uri));
                }
                jmsServletRegistration.setInitialized(true);
            } catch (ServletException e) {
                logger.error(String.format("Servlet initialization failed, servlet name is: %s.", jmsServletRegistration.getName()), e);
            }
        }
        logger.info("Servlet initialization is complete.");
    }

    @Override
    public void initFilters() {
        logger.info("Start initializing the filter.");
        for (JmsFilterRegistration jmsFilterRegistration: filterRegistrationMap.values()) {
            try {
                if (jmsFilterRegistration.isInitialized()) {
                    continue;
                }
                //初始化Filter
                jmsFilterRegistration.getFilter().init(jmsFilterRegistration.getFilterConfig());
                //添加servletMapping
                for (String uri: jmsFilterRegistration.getUrlPatternMappings()) {
                    filterMappingList.add(new FilterMapping(jmsFilterRegistration.getFilter(), uri));
                }
                jmsFilterRegistration.setInitialized(true);
            } catch (ServletException e) {
                logger.error(String.format("Servlet initialization failed, servlet name is: %s.", jmsFilterRegistration.getName()), e);
            }
        }
        logger.info("Filter initialization is complete.");
    }


    @Override
    public void registerListener() {

    }

    @Override
    public void initListeners() {

    }

    public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String path = request.getRequestURI();
        // search servlet:
        Servlet servlet = getServletByPath(path);
        if (servlet == null) {
            // 404 Not Found:
            throw new ServletException(JmsServletError.NOT_FOUND);
        }
        Filter[] filters = getFilters(path);
        FilterChain filterChain = new JmsFilterChain(filters, servlet);
        filterChain.doFilter(request, response);
    }

    /**
     * 根据uri获取servlet
     * @param path uri
     * @return Servlet
     */
    public Servlet getServletByPath(String path) {
        Servlet servlet = null;
        for (ServletMapping mapping : servletMappingList) {
            if (mapping.matcher(path)) {
                servlet = mapping.get();
            }
        }
        return servlet;
    }



    /**
     * 根据请求找到要使用的过滤器
     * @param uri 请求uri
     * @return HttpFilter[]
     */
    private Filter[] getFilters(String uri) {
        List<Filter> filterList = new ArrayList<>();
        for (FilterMapping mapping : filterMappingList) {
            if (mapping.matcher(uri)) {
                filterList.add(mapping.get());
            }
        }
        return filterList.toArray(new Filter[0]);
    }

    public String[] getStaticResourcePath() {
        return staticResourcePath;
    }

    @Override
    public String getContextPath() {
        return null;
    }

    @Override
    public ServletContext getContext(String uriPath) {
        return null;
    }

    @Override
    public int getMajorVersion() {
        return 0;
    }

    @Override
    public int getMinorVersion() {
        return 0;
    }

    @Override
    public int getEffectiveMajorVersion() {
        return 0;
    }

    @Override
    public int getEffectiveMinorVersion() {
        return 0;
    }

    @Override
    public String getMimeType(String file) {
        return null;
    }

    @Override
    public Set<String> getResourcePaths(String path) {
        return null;
    }

    @Override
    public URL getResource(String path) {
        return null;
    }

    @Override
    public InputStream getResourceAsStream(String path) {
        return null;
    }

    @Override
    public RequestDispatcher getRequestDispatcher(String path) {
        return null;
    }

    @Override
    public RequestDispatcher getNamedDispatcher(String name) {
        return null;
    }

    @Override
    public Servlet getServlet(String name) {
        return servletRegistrationMap.get(name).getServlet();
    }

    @Override
    public Enumeration<Servlet> getServlets() {
        return null;
    }

    @Override
    public Enumeration<String> getServletNames() {
        return null;
    }

    @Override
    public void log(String msg) {

    }

    @Override
    public void log(Exception exception, String msg) {

    }

    @Override
    public void log(String message, Throwable throwable) {

    }

    @Override
    public String getRealPath(String path) {
        return null;
    }

    @Override
    public String getServerInfo() {
        return null;
    }

    @Override
    public String getInitParameter(String name) {
        return null;
    }

    @Override
    public Enumeration<String> getInitParameterNames() {
        return null;
    }

    @Override
    public boolean setInitParameter(String name, String value) {
        return false;
    }

    @Override
    public Object getAttribute(String name) {
        return null;
    }

    @Override
    public Enumeration<String> getAttributeNames() {
        return null;
    }

    @Override
    public void setAttribute(String name, Object object) {

    }

    @Override
    public void removeAttribute(String name) {

    }

    @Override
    public String getServletContextName() {
        return null;
    }

    @SuppressWarnings("unchecked")
    @Override
    public ServletRegistration.Dynamic addServlet(String servletName, String className) {
        if (className == null || className.isEmpty()) {
            throw new IllegalArgumentException("className is null or empty.");
        }
        try {
            return addServlet(servletName, (Class<? extends Servlet>) Class.forName(className));
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet) {
        if (servletName == null) {
            throw new IllegalArgumentException("name is null.");
        }
        if (servlet == null) {
            throw new IllegalArgumentException("servlet is null.");
        }
        JmsServletRegistration registration = new JmsServletRegistration(this, servlet, servletName);
        servletRegistrationMap.put(servletName, registration);
        return registration;
    }

    @Override
    public ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass) {
        if (servletClass == null) {
            throw new IllegalArgumentException("servletClass is null.");
        }
        try {
            return addServlet(servletName, servletClass.newInstance());
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public ServletRegistration.Dynamic addJspFile(String servletName, String jspFile) {
        return null;
    }

    @Override
    public <T extends Servlet> T createServlet(Class<T> clazz) {
        return null;
    }

    @Override
    public ServletRegistration getServletRegistration(String servletName) {
        return null;
    }

    @Override
    public Map<String, ? extends ServletRegistration> getServletRegistrations() {
        return servletRegistrationMap;
    }

    @SuppressWarnings("unchecked")
    @Override
    public FilterRegistration.Dynamic addFilter(String filterName, String className) {
        if (className == null || className.isEmpty()) {
            throw new IllegalArgumentException("className is null or empty.");
        }
        try {
            return addFilter(filterName, (Class<? extends Filter>) Class.forName(className));
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public FilterRegistration.Dynamic addFilter(String filterName, Filter filter) {
        if (filterName == null) {
            throw new IllegalArgumentException("name is null.");
        }
        if (filter == null) {
            throw new IllegalArgumentException("servlet is null.");
        }
        JmsFilterRegistration registration = new JmsFilterRegistration(this, filter, filterName);
        filterRegistrationMap.put(filterName, registration);
        return registration;
    }

    @Override
    public FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> filterClass) {
        if (filterClass == null) {
            throw new IllegalArgumentException("servletClass is null.");
        }
        try {
            return addFilter(filterName, filterClass.newInstance());
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public <T extends Filter> T createFilter(Class<T> clazz) {
        return null;
    }

    @Override
    public FilterRegistration getFilterRegistration(String filterName) {
        return null;
    }

    @Override
    public Map<String, ? extends FilterRegistration> getFilterRegistrations() {
        return null;
    }

    @Override
    public SessionCookieConfig getSessionCookieConfig() {
        return null;
    }

    @Override
    public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes) {

    }

    @Override
    public Set<SessionTrackingMode> getDefaultSessionTrackingModes() {
        return null;
    }

    @Override
    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() {
        return null;
    }

    @Override
    public void addListener(String className) {

    }

    @Override
    public <T extends EventListener> void addListener(T t) {

    }

    @Override
    public void addListener(Class<? extends EventListener> listenerClass) {

    }

    @Override
    public <T extends EventListener> T createListener(Class<T> clazz) {
        return null;
    }

    @Override
    public JspConfigDescriptor getJspConfigDescriptor() {
        return null;
    }

    @Override
    public ClassLoader getClassLoader() {
        return null;
    }

    @Override
    public void declareRoles(String... roleNames) {

    }

    @Override
    public String getVirtualServerName() {
        return null;
    }

    @Override
    public int getSessionTimeout() {
        return 0;
    }

    @Override
    public void setSessionTimeout(int sessionTimeout) {

    }

    @Override
    public String getRequestCharacterEncoding() {
        return null;
    }

    @Override
    public void setRequestCharacterEncoding(String encoding) {

    }

    @Override
    public String getResponseCharacterEncoding() {
        return null;
    }

    @Override
    public void setResponseCharacterEncoding(String encoding) {

    }
}
